#ifdef GLUT_INTERFACE

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ciostream>
#include <cctype>

#if defined(_WIN32)
#include <windows.h>
#endif

#include <gl/GL.h>
#include <gl/GLU.h>
#include <gl/GLUT.h>

#include "GLUTInterface.h"
#include "SweepDescription.h"
#include "CutPerformer.h"
#include "FEMModel.h"
#include "FEMDrawer.h"
#include "Triangle.h"
#include "Node.h"
#include "Types.h"
#include "Matrix.h"
#include "Misc.h"
#include "SurfaceSelector.h"
#include "Tetrahedron.h"
#include "Scenario.h"
#include "Component.h"
#include "ModelIO.h"
#include "Statistics.h"
#include "DeformationState.h"
#include "InitForce.h"
#include "Ray.h"

static char marker_a_[10] = "";
static DragState *track_this = 0;
static int lastbutton;
static int lastx , lasty;
static int screen_height ;
static int screen_width ;
FEMDrawer *the_global_drawer = 0;
bool rgb = true;
int marker_len_;
SimplexType mark_what_;
bool shift_b;

Real fsign = 0.01;
Real INCR =    5;
Real DX = 0.1; 

static void HandleKey (unsigned char,int,int);

void * StartVisualisation (void *fem, int argc, char**argv)
{
	the_global_drawer = new FEMDrawer ((FiniteElementModel*)fem);

	GLUTSetup (the_global_drawer, argc, argv);

	cout <<"visualization finished"<<endl;
	return 0;
}


void MarkFeature ()
{
	if (!marker_a_[0])
	{
		the_global_drawer->marked_ =0;
		cout << "unmarking " << TopologicalObject::Name (mark_what_);
		return;
	}

	int i=0;
	int succ = sscanf (marker_a_, "%d", &i);
	if (!succ)
		return;

	cout << "marking " << TopologicalObject::Name (mark_what_) << " " << i<<endl;

	if (mark_what_ == NODE)
	{
		Node *n =  the_global_drawer->fem_->GetNode (i);
		the_global_drawer->SetMarked (0, n);
	}
	else
	{
		TopologicalObject * o = the_global_drawer->fem_->FindTopolObject (i, mark_what_);
		the_global_drawer->SetMarked (o, 0);
	}

	if (!the_global_drawer->marked_  && !the_global_drawer->marked_node_)
	{
		int l = strlen (marker_a_) ;
		marker_a_[0] = l > 1 ? marker_a_[l-1] : '0';
		marker_a_[1] = 0;
		MarkFeature ();
	}
}

void CycleMark ()
{
	marker_a_[0] = 0;
	MarkFeature ();

	mark_what_ = NextType (mark_what_);
	cout << "Now marking " << TopologicalObject::Name (mark_what_) << endl;
}

// GLUTCALLBACK
void IdleFunc (void)
{
	glutPostRedisplay();
}

void VisibleFunc (int vis)
{
	if (vis == GLUT_VISIBLE)
		glutIdleFunc(IdleFunc);
	else
		glutIdleFunc(NULL);
}

static void Reshape(int w, int h)
{
	screen_width= w; screen_height = h;
	glViewport(0, 0, (GLint)w, (GLint)h);
}

static void Animate(void)
{
	the_global_drawer->draw();
	glutSwapBuffers();
	//  usleep (100000);  
}


static void Timer (int )
{
}

static void HandleKey(unsigned char key, int a, int b)
{
	if (isdigit (key))
	{
		int l = strlen (marker_a_);
		marker_a_[l] = key;		// ugh. buffer overflow.
		marker_a_[l+1] = 0;
		MarkFeature ();
		return ;
	}

	if (key=='\r')
	{
		marker_a_[0] = 0;
		return; 
	}


	if(key == 'f')
		track_this = &the_global_drawer->force_drag_;
	else if (key == 'c')
		track_this = &the_global_drawer->cut_drag_;
	else if (key == 'v')
		track_this = &the_global_drawer->view_drag_;
	else if (key == 'l')
		track_this = &the_global_drawer->light_drag_;


	FEMDrawer *draw = the_global_drawer;
	switch (key)
	{
	case 'c':
		draw->NextCut ();
		break;

	case 'F':
		SetInitialForce (draw->fem_);
		break;

	case '':
		/*
		Reverse  the  force direction  to  check if  we  get  back to  the
		original situation.

		TODO: do this in the interface.
		*/
		SetNumberSetting ("init-force", -GetNumberSetting ("init-force"));
		break;
	case 'C':
		draw->StopCut ();
		break;

	case 'o':
		draw->ToggleOriginal();
		break; 
	case 'B':
		draw->ToggleShowBack ();
		break;

	case 'd':
		draw-> ToggleFog ();
		break;

	case '\e':
		exit (0);
		break;

	case 'e':
		draw->  ToggleEdge ();
		break;

	case 'g':
		draw-> Fix ();
		break;

	case 'G':
		draw->Unfix();
		break;

	case 'i':
		draw->ToggleInside ();
		break;


	case 'q':
		draw->ToggleTetQuality ();
		break;

	case 'L':
		draw->ToggleLighting (); 
		break;

	case 'N':
		draw->ToggleSmooth ();
		break;

	case 'n':
		draw->ToggleNormal();
		break;


	case 'p':
		draw->    PrintMarked ();
		break;


	case 'Q':

		quiet_global_b = !quiet_global_b;
		cout << "Quiet set to "<< quiet_global_b <<endl;
		break;

	case 'r':
		draw->ToggleRelaxation();
		break;

	case 's':
		draw->ToggleSurfaceSelector ();
		break;


	case 'S':
		draw->ToggleSolid();
		break;

	case 't':
		//	 draw->test_feature ();
		break;

	case 'w':
		WriteFEM ( draw->fem_, "out.model");
		WriteDeformationState ( draw->fem_->pstate_, "out.model.state");
		WriteBoundary ( draw->fem_, "out.model.boundary");
		break;

	case '-':
		draw->view_drag_.scale_(0) += 0.25;
		draw->SetFrustrum ();
		break;


	case '+':
		draw->SetFrustrum ();
		break;

	case 'Z':
		draw->view_drag_.translate_(2) += DX;
		draw->SetCamera();
		break;
	case 'z':
		draw->view_drag_.translate_(2) -= DX;
		draw->SetCamera ();
		break;

	case '=':
		cout << draw->Stats ();
		break;

	case 'm':
		CycleMark ();
		break;


	default:
		printf ("DEFULT");
		break;

	}
	glutPostRedisplay();
}


static void Motion (int x,int y)
{
	if (lastbutton == -1)
		the_global_drawer->RotateDrag (&the_global_drawer->view_drag_.quat_,
		screen_width, screen_height,
		lastx, lasty,
		x, y);
	if (lastbutton == 0)
	{
		the_global_drawer->RotateDrag (&track_this->quat_,
			screen_width, screen_height,
			lastx, lasty,
			x, y);
	}
	else if (shift_b && lastbutton == 1)
	{
		the_global_drawer->TranslateDrag (&track_this->translate_,
			screen_width, screen_height,
			lastx, lasty,
			x, y);
	}
	else if (lastbutton == 1)
	{
		the_global_drawer->ScaleDrag (&track_this->scale_,
			screen_width, screen_height,
			lastx, lasty,
			x, y);
	}

	lastx = x;
	lasty = y;
	glutPostRedisplay();  
}

static void MouseEvent (int button, int state, int x, int y)
{
	lastx = x;
	lasty = y;

	int b = 0;
	int s = 0;
	switch (button) {
		  case GLUT_MIDDLE_BUTTON:
			  b = 0;break;
		  case GLUT_LEFT_BUTTON:
			  b = -1; break;
		  case GLUT_RIGHT_BUTTON:
			  b = 1; break;
	}
	lastbutton = b;

	switch (state) {
		  case GLUT_UP:
			  s = 1;break;
		  case GLUT_DOWN:
			  s = -1; break;
	}

	shift_b = (glutGetModifiers () & GLUT_ACTIVE_SHIFT);



	if (b == 0 && s == 1 && track_this == &the_global_drawer->cut_drag_)
		the_global_drawer->ShowCut ();

	// the_global_drawer->handle_mouse_button (b,s ,x,y);
}



static void SpecialKey (int key , int x, int y)
{
	int axis = -1;
	int dir = 0;


	switch (key)
	{
	case GLUT_KEY_LEFT:
		axis =0;
		dir = -1;
		break;
	case GLUT_KEY_UP:
		axis = 1;
		dir = 1;
		break;
	case GLUT_KEY_RIGHT:
		axis = 0;
		dir = 1;
		break;
	case GLUT_KEY_DOWN:
		axis = 1;
		dir = -1;
		break;
	default:
		break;
	}

	if (axis == 0)
	{
		if (the_global_drawer->cut_performer_)
		{
			the_global_drawer->	  cut_performer_->RevertSweepTo (the_global_drawer->last_sweep_);

			the_global_drawer->	  velocity_ *= (dir == 1) ?  1.2 : 1/(1.2);

			//	  this_sweep_ = pending_sweep (this, scalpel_velocity_);
			the_global_drawer->	  cut_performer_->SetNextSweep (the_global_drawer->this_sweep_);

			the_global_drawer->	  cut_performer_->TryCut ();
		}
	}

	glutPostRedisplay();
}


void GLUTSetup (FEMDrawer* fd, int argc, char * argv[])
{
	the_global_drawer = fd;
	glutInit(&argc, argv);

	printf ("\n\nSetting up GLUT OpenGL display.\n");

	int type = GLUT_DEPTH | GLUT_RGB | GLUT_DOUBLE;
	if (stereo_global_b)
		type |= GLUT_STEREO;

	glutInitDisplayMode(type);

	int ws = (int) GetNumberSetting ("window-size");
	int wp = (int) GetNumberSetting ("window-position");

	if (wp >= 0)
		glutInitWindowPosition (wp, wp);
	glutInitWindowSize (ws, ws);
	if (glutCreateWindow("SALMON") == GL_FALSE) {
		exit(1);
	}

	glutReshapeFunc(&reshape);
	glutKeyboardFunc(&handle_key);
	glutDisplayFunc(&animate);
	glutMouseFunc(&mouse_event);
	glutMotionFunc(&motion);
	glutSpecialFunc (&special_key);
	glutVisibilityFunc (&VisibleFunc);
	glutTimerFunc(100, timer, 0);
	glutIdleFunc(IdleFunc);

	fd->Setup ();

	track_this = &fd-> view_drag_;

	glutMainLoop();
}

#else

#include "FEMModel.h"

void  * StartVisualisation (int, char**, FiniteElementModel*)
{
	return 0;
}
#endif
